from typing import Any

from sqlalchemy import select, false, distinct
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm.strategy_options import _AbstractLoad, joinedload

from apps.functional import schemas
from apps.models import FunctionalTestcase, FunctionalTestcaseStep, SysVersion
from core import status
from core.crud import DalBase
from core.data_types import datetime_str_vali
from core.exceptions import CustomException


class TestcaseStepDal(DalBase):
    def __init__(self, db: AsyncSession):
        super(TestcaseStepDal, self).__init__()
        self.db = db
        self.model = FunctionalTestcaseStep
        self.schema = schemas.TestcaseStepOut

    async def delete_by_condition(self, testcase_id: int, version_id: int):
        await self.delete_datas_condition([self.model.testcase_id == testcase_id, self.model.version_id == version_id])

    async def steps_with_sorted(self, testcase_id: int, version_id: int, order_filed: str):
        sql = select(self.model).order_by(order_filed).where(self.model.version_id == version_id,
                                                             self.model.testcase_id == testcase_id)
        queryset = await self.db.scalars(sql)
        datas = list(queryset.all())
        return [await self.out_dict(i, v_schema=self.schema) for i in datas]

    async def versions(self, testcase_id: int):
        sql = (select(
            distinct(self.model.version_id),
            self.model.create_time,
            SysVersion.name,
            SysVersion.order)
               .select_from(FunctionalTestcaseStep)
               .join(SysVersion,
                     self.model.version_id == SysVersion.id)
               .where(self.model.testcase_id == testcase_id))
        datas = await self.db.execute(sql)
        return [{'id': data[0], 'name': data[2], 'timestamp': datetime_str_vali(data[1]), 'order': data[3]} for
                data in datas]


class TestcaseDal(DalBase):

    def __init__(self, db: AsyncSession):
        super(TestcaseDal, self).__init__()
        self.db = db
        self.model = FunctionalTestcase
        self.schema = schemas.TestcaseSimpleOut

    async def create_data(
            self,
            data: schemas.Testcase,
            v_options: list[_AbstractLoad] = None,
            v_return_obj: bool = False,
            v_schema: Any = None
    ) -> Any:
        try:
            obj_testcase = self.model(**data.model_dump(exclude={'steps'}))
            self.db.add(obj_testcase)
            await self.flush(obj_testcase)
            for step in data.steps:
                obj_step = FunctionalTestcaseStep(**step.model_dump())
                obj_step.testcase_id = obj_testcase.id
                self.db.add(obj_step)
            await self.flush()
            return await self.out_dict(obj=obj_testcase,
                                       v_options=[joinedload(self.model.version),
                                                  joinedload(self.model.steps)]
                                       )
        except Exception as e:
            await self.db.rollback()
            raise CustomException(f"新增用例失败！原因:{e}", code=status.HTTP_ERROR)

    async def list_testcases(self, data: schemas.TestcaseCatalogs, **kwargs):
        return await self.get_datas(v_options=[joinedload(self.model.steps),
                                               joinedload(self.model.version),
                                               joinedload(self.model.max_version)],
                                    v_return_count=True,
                                    v_where=[self.model.catalog_id.in_(data.catalog_ids)] if data.catalog_ids else [],
                                    **kwargs)

    async def testcase_info(self, data_id, **kwargs):
        sql = select(self.model).where(self.model.is_delete == false(), self.model.id == data_id).options()
        options = [joinedload(self.model.steps), joinedload(self.model.version), joinedload(self.model.max_version)]
        sql = sql.options(*options)
        queryset = await self.db.scalar(sql)
        testcase = self.schema.model_validate(queryset).model_dump(exclude={'steps'})
        steps = await TestcaseStepDal(self.db).steps_with_sorted(data_id, kwargs.get('version_id'), 'num')
        testcase['steps'] = steps
        return testcase

    async def update_testcase(self, data_id: int, data: schemas.Testcase):
        try:
            obj_testcase = self.model(**data.model_dump(exclude={'steps'}))
            testcase = await self.put_data(data_id=data_id, data=obj_testcase,
                                           v_options=[joinedload(self.model.version),
                                                      joinedload(self.model.steps)]
                                           )
            # ids = [step.get('id') for step in testcase.get('steps') if
            #        step.get('version_id') == testcase.get('version_id')]
            await TestcaseStepDal(self.db).delete_by_condition(
                testcase_id=data_id,
                version_id=testcase.get('version_id')
            )
            for step in data.steps:
                obj_step = FunctionalTestcaseStep(**step.model_dump(exclude={'id'}))
                obj_step.testcase_id = data_id
                self.db.add(obj_step)
            await self.flush()
            return await self.out_dict(obj=obj_testcase,
                                       v_options=[joinedload(self.model.version),
                                                  joinedload(self.model.steps)]
                                       )
        except Exception as e:
            await self.db.rollback()
            raise CustomException(f"修改用例失败！原因:{e}", code=status.HTTP_ERROR)

    async def testcase_versions(self, data_id):
        versions = await TestcaseStepDal(self.db).versions(data_id)
        return versions

    async def change_testcase_version(self, data):
        await self.put_data(data.id, data, v_return_obj=True)

    async def new_testcase_version(self, data: schemas.NewTestcaseVersion):
        try:
            for step in data.steps:
                obj_step = FunctionalTestcaseStep(**step.model_dump())
                obj_step.version_id = data.version_id
                obj_step.testcase_id = data.id
                self.db.add(obj_step)
            await self.flush()
            tvd = {'id': data.id, 'version_id': data.version_id, 'max_version_id': data.max_version_id}
            await self.change_testcase_version(schemas.TestcaseVersion.parse_obj(tvd))
            return await self.testcase_info(data.id, version_id=data.version_id)
        except Exception as e:
            await self.db.rollback()
            raise CustomException(f"新建版本失败！原因:{e}", code=status.HTTP_ERROR)

    async def delete_testcase_version(self, data: schemas.TestcaseVersion):
        await TestcaseStepDal(self.db).delete_by_condition(data.id, data.version_id)
